{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZwZNOAMZcxl3"
      },
      "source": [
        "##### Copyright 2019 Google LLC"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "nxbcnXODdE06"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-BszoQj0dSZO"
      },
      "source": [
        "# 画像分類のための敵対的正則化"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wfqlePz0g6o5"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"> TensorFlow.orgで表示</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"> Google Colab で実行</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示{</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oHEGl8h_m6tS"
      },
      "source": [
        "## 概要\n",
        "\n",
        "このチュートリアルでは、Neural Structured Learning（NSL）フレームワークを用いた画像分類のための敵対的学習（[Goodfellow et al.、2014](https://arxiv.org/abs/1412.6572)）の使用について説明します。\n",
        "\n",
        "敵対的学習の中核となる考え方は、有機的な学習データに加え、敵対的なデータ（敵対的サンプルと呼ばれる）を用いてモデルをトレーニングすることです。これらの敵対的サンプルは人間の目には元のデータと同じように見えますが、摂動がモデルを混乱させ、誤った予測や分類が行われる原因となります。敵対的サンプルはモデルに誤った予測や分類を行わせ、誤認識させるように意図的に構成されています。このような例を用いてトレーニングを行うことによって、モデルは予測を行う際に敵対的摂動に対してロバストになるよう学習します。\n",
        "\n",
        "このチュートリアルでは、Neural Structured Learning のフレームワークを使用し、ロバストなモデルを得るために敵対的学習を適用する手順を以下に説明します。\n",
        "\n",
        "1. 基本モデルとしてニューラルネットワークを作成します。このチュートリアルでは、`tf.keras` Functional API で基本モデルを作成します。この手順は、`tf.keras` Sequential API および Subclassing API で作成されたモデルと互換性があります。TensorFlow における Keras モデルの詳細については、こちらの[ドキュメント](https://www.tensorflow.org/api_docs/python/tf/keras/Model)をご覧ください。\n",
        "2. NSL フレームワークが提供するラッパークラス **`AdversarialRegularization`** で基本モデルをラップして、新しい `tf.keras.Model` インスタンスを作成します。この新しいモデルには、トレーニング目的の正則化項として敵対的損失が含まれます。\n",
        "3. トレーニングデータの例を特徴ディクショナリに変換します。\n",
        "4. 新しいモデルをトレーニングして評価します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VgSOF-49Q7kS"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4RhmgQ7-mlrl"
      },
      "source": [
        "Neural Structured Learning パッケージをインストールします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ByJ7133BQULR"
      },
      "outputs": [],
      "source": [
        "!pip install --quiet neural-structured-learning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PZvsEQrhSqKx"
      },
      "source": [
        "ライブラリをインポートします。`neural_structured_learning` を `nsl` と略します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EuqEuAYzTMo0"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import neural_structured_learning as nsl\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import tensorflow_datasets as tfds"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3LwBtQGaTvbe"
      },
      "source": [
        "## ハイパーパラメータ\n",
        "\n",
        "モデルのトレーニングと評価のための（`HParams` オブジェクト内の）ハイパーパラメータを集めて説明します。\n",
        "\n",
        "入力/出力：\n",
        "\n",
        "- **`input_shape`**：入力テンソルの形状。各画像は 28×28 ピクセルで 1 チャンネルです。\n",
        "- **`num_classes`**：[0-9] に対応する数字 10 個分があり、合計 10 クラスです。\n",
        "\n",
        "モデルアーキテクチャ：\n",
        "\n",
        "- **`conv_filters`**：各畳み込みレイヤーのフィルタ数を指定する数値のリスト。\n",
        "- **`kernel_size`**：すべての畳み込みレイヤーで共有する 2 次元の畳み込みウィンドウのサイズ。\n",
        "- **`pool_size`**：各最大 Pooling レイヤーで画像をダウンスケールするための係数。\n",
        "- **`num_fc_units`**：完全に接続された各レイヤーの単位（すなわち幅）の数。\n",
        "\n",
        "トレーニングと評価：\n",
        "\n",
        "- **`batch_size`**：トレーニングや評価に使用するバッチサイズ。\n",
        "- **`epochs`**：トレーニングのエポック数。\n",
        "\n",
        "敵対的学習：\n",
        "\n",
        "- **`adv_multiplier`**：ラベル付けされた損失に対して相対的な、トレーニング目的内の敵対的損失の重み。\n",
        "- **`adv_step_size`**：敵対的摂動の大きさ。\n",
        "- **`adv_grad_norm`**：敵対的摂動の大きさを測るノルム。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iOc8YdmIRSHo"
      },
      "outputs": [],
      "source": [
        "class HParams(object):\n",
        "  def __init__(self):\n",
        "    self.input_shape = [28, 28, 1]\n",
        "    self.num_classes = 10\n",
        "    self.conv_filters = [32, 64, 64]\n",
        "    self.kernel_size = (3, 3)\n",
        "    self.pool_size = (2, 2)\n",
        "    self.num_fc_units = [64]\n",
        "    self.batch_size = 32\n",
        "    self.epochs = 5\n",
        "    self.adv_multiplier = 0.2\n",
        "    self.adv_step_size = 0.2\n",
        "    self.adv_grad_norm = 'infinity'\n",
        "\n",
        "HPARAMS = HParams()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "72zL1AMcYYGG"
      },
      "source": [
        "## MNIST データセット\n",
        "\n",
        "[MNIST データセット](http://yann.lecun.com/exdb/mnist/)には手書きの数字（ '0' から '9' まで）のグレースケール画像が含まれています。各画像は低解像度（28×28 ピクセル）で数字 1 文字を示しています。このタスクでは、画像を数字ごとに 1 つずつ、10 のカテゴリに分類します。\n",
        "\n",
        "ここで [TensorFlow Datasets](https://www.tensorflow.org/datasets) から MNIST データセットを読み込みます。これはデータのダウンロードと `tf.data.Dataset` の構築処理をします。読み込んだデータセットには 2 つのサブセットがあります。\n",
        "\n",
        "- 6 万個の例を含む `train`\n",
        "- 1 万個の例を含む `test`\n",
        "\n",
        "両方のサブセットに含まれる例は、以下の 2 つのキーを持つ特徴ディクショナリに格納されています。\n",
        "\n",
        "- `image`：0 から 255 までのピクセル値の配列。\n",
        "- `label`：0 から 9までの真の正解ラベル。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R1dK6E4axNHB"
      },
      "outputs": [],
      "source": [
        "datasets = tfds.load('mnist')\n",
        "\n",
        "train_dataset = datasets['train']\n",
        "test_dataset = datasets['test']\n",
        "\n",
        "IMAGE_INPUT_NAME = 'image'\n",
        "LABEL_INPUT_NAME = 'label'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IBkh4mbsxLR_"
      },
      "source": [
        "モデルを数値的に安定させるには、`normalize` 関数にデータセットをマッピングし、ピクセル値を [0, 1] に正規化します。トレーニングセットをシャッフルしてバッチ処理を行った後、基本モデルをトレーニングするために例を特徴タプル `(image, label)` に変換します。また、後で使用できるようにタプルをディクショナリに変換する関数も用意しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VhMEJqKs0_7z"
      },
      "outputs": [],
      "source": [
        "def normalize(features):\n",
        "  features[IMAGE_INPUT_NAME] = tf.cast(\n",
        "      features[IMAGE_INPUT_NAME], dtype=tf.float32) / 255.0\n",
        "  return features\n",
        "\n",
        "def convert_to_tuples(features):\n",
        "  return features[IMAGE_INPUT_NAME], features[LABEL_INPUT_NAME]\n",
        "\n",
        "def convert_to_dictionaries(image, label):\n",
        "  return {IMAGE_INPUT_NAME: image, LABEL_INPUT_NAME: label}\n",
        "\n",
        "train_dataset = train_dataset.map(normalize).shuffle(10000).batch(HPARAMS.batch_size).map(convert_to_tuples)\n",
        "test_dataset = test_dataset.map(normalize).batch(HPARAMS.batch_size).map(convert_to_tuples)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JrrMpPNmpCKK"
      },
      "source": [
        "## 基本モデル\n",
        "\n",
        "基本モデルは、3 つの畳み込みレイヤーと 2 つの完全に接続されたレイヤー（`HPARAMS` で定義されている）から成るニューラルネットワークです。ここでは Keras の Functional API を使用して定義しています。他の API やモデルアーキテクチャを自由に試してみてください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4UjrtuIsYWo3"
      },
      "outputs": [],
      "source": [
        "def build_base_model(hparams):\n",
        "  \"\"\"Builds a model according to the architecture defined in `hparams`.\"\"\"\n",
        "  inputs = tf.keras.Input(\n",
        "      shape=hparams.input_shape, dtype=tf.float32, name=IMAGE_INPUT_NAME)\n",
        "\n",
        "  x = inputs\n",
        "  for i, num_filters in enumerate(hparams.conv_filters):\n",
        "    x = tf.keras.layers.Conv2D(\n",
        "        num_filters, hparams.kernel_size, activation='relu')(\n",
        "            x)\n",
        "    if i < len(hparams.conv_filters) - 1:\n",
        "      # max pooling between convolutional layers\n",
        "      x = tf.keras.layers.MaxPooling2D(hparams.pool_size)(x)\n",
        "  x = tf.keras.layers.Flatten()(x)\n",
        "  for num_units in hparams.num_fc_units:\n",
        "    x = tf.keras.layers.Dense(num_units, activation='relu')(x)\n",
        "  pred = tf.keras.layers.Dense(hparams.num_classes, activation='softmax')(x)\n",
        "  model = tf.keras.Model(inputs=inputs, outputs=pred)\n",
        "  return model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "288nsmN5pLoo"
      },
      "outputs": [],
      "source": [
        "base_model = build_base_model(HPARAMS)\n",
        "base_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mlTUGn1t_HAr"
      },
      "source": [
        "次に、基本モデルのトレーニングと評価を行います。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K2cFDbmRpRMp"
      },
      "outputs": [],
      "source": [
        "base_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',\n",
        "                   metrics=['acc'])\n",
        "base_model.fit(train_dataset, epochs=HPARAMS.epochs)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "J94Y_WTaqAsi"
      },
      "outputs": [],
      "source": [
        "results = base_model.evaluate(test_dataset)\n",
        "named_results = dict(zip(base_model.metrics_names, results))\n",
        "print('\\naccuracy:', named_results['acc'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c8OClWqGALIm"
      },
      "source": [
        "基本モデルがテストセットで 99% の精度を達成していることが分かります。下記の[敵対的摂動下でのロバスト性](#scrollTo=HXK9MGG8lBX3)では、モデルがどれだけロバストであるかを確認します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CemXA8N9q336"
      },
      "source": [
        "## 敵対的正則化モデル\n",
        "\n",
        "ここでは、NSL フレームワークを使用して、数行のコードで Keras モデルに敵対的トレーニングを組み込む方法を示します。基本モデルをラップして、トレーニング目的に敵対的正則化を含む新しい `tf.Keras.Model` を作成します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YUOpl-rkzRrY"
      },
      "source": [
        "まず最初に、ヘルパー関数 `nsl.configs.make_adv_reg_config` を使用して、関連するすべてのハイパーパラメータを含む構成オブジェクトを作成します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-WWVwJB2qstE"
      },
      "outputs": [],
      "source": [
        "adv_config = nsl.configs.make_adv_reg_config(\n",
        "    multiplier=HPARAMS.adv_multiplier,\n",
        "    adv_step_size=HPARAMS.adv_step_size,\n",
        "    adv_grad_norm=HPARAMS.adv_grad_norm\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OmeIUyxE4s68"
      },
      "source": [
        "これで `AdversarialRegularization` を使って基本モデルをラップすることができます。ここでは、既存の基本モデル（`base_model`）を後で比較に使用できるように、新しい基本モデル（`base_adv_model`）を作成します。\n",
        "\n",
        "返される `adv_model` は `tf.keras.Model` のオブジェクトであり、そのトレーニング目的には敵対的損失の正則化項を含みます。この損失を計算するためには、モデルは通常の正規入力（特徴 `image`）に加えてラベル情報（特徴 `label`）にアクセスする必要があります。この理由から、データセットの例は変換してタプルからディクショナリに戻します。そして `label_keys` パラメータを介してどの特徴がラベル情報を含んでいるかをモデルに伝えます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TObqJLEX4sQq"
      },
      "outputs": [],
      "source": [
        "base_adv_model = build_base_model(HPARAMS)\n",
        "adv_model = nsl.keras.AdversarialRegularization(\n",
        "    base_adv_model,\n",
        "    label_keys=[LABEL_INPUT_NAME],\n",
        "    adv_config=adv_config\n",
        ")\n",
        "\n",
        "train_set_for_adv_model = train_dataset.map(convert_to_dictionaries)\n",
        "test_set_for_adv_model = test_dataset.map(convert_to_dictionaries)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aKTQWzfj7JvL"
      },
      "source": [
        "次に、敵対的正則化モデルをコンパイルしてトレーニングし、評価します。「損失ディクショナリに出力がありません」というような警告が出るかもしれませんが、これは `adv_model` が基本実装に依存せずトータルの損失を計算しているため、問題ありません。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aTSK-cHbuWDw"
      },
      "outputs": [],
      "source": [
        "adv_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',\n",
        "                   metrics=['acc'])\n",
        "adv_model.fit(train_set_for_adv_model, epochs=HPARAMS.epochs)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3v_Jn7wuviZx"
      },
      "outputs": [],
      "source": [
        "results = adv_model.evaluate(test_set_for_adv_model)\n",
        "named_results = dict(zip(adv_model.metrics_names, results))\n",
        "print('\\naccuracy:', named_results['sparse_categorical_accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LgnslZYk9Acg"
      },
      "source": [
        "敵対的正則化モデルもまた、テストセットで非常に優れた性能（99% の精度）を達成していることが分かります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HXK9MGG8lBX3"
      },
      "source": [
        "## 敵対的摂動下におけるロバスト性\n",
        "\n",
        "ここでは基本モデルと敵対的正則化モデルの、敵対的摂動下におけるロバスト性を比較します。\n",
        "\n",
        "敵対的摂動の例の生成には `AdversarialRegularization.perturb_on_batch` 関数を使用します。そして、これは基本モデルを基にして生成します。これを行うためには、`AdversarialRegularization` を使用して基本モデルをラップします。トレーニング（`Model.fit`）を呼び出さない限り、モデル内の学習変数に変更はなく、モデルは[基本モデル](#scrollTo=JrrMpPNmpCKK)のセクションのものと同じであることに注意してください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FLkYw54pvxJO"
      },
      "outputs": [],
      "source": [
        "reference_model = nsl.keras.AdversarialRegularization(\n",
        "    base_model,\n",
        "    label_keys=[LABEL_INPUT_NAME],\n",
        "    adv_config=adv_config)\n",
        "reference_model.compile(\n",
        "    optimizer='adam',\n",
        "    loss='sparse_categorical_crossentropy',\n",
        "    metrics=['acc'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DR0Rn5rxBeDh"
      },
      "source": [
        "評価対象のモデルをディクショナリに収集し、各モデルのメトリックオブジェクトを作成します。\n",
        "\n",
        "基本モデルと同じ（ラベル情報が不要な）入力形式を持つために `adv_model.base_model` を取る必要があることに注意してください。`adv_model.base_model` で学習する変数は、`adv_model` で学習する変数と同じです。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "igRBxPlPm_JE"
      },
      "outputs": [],
      "source": [
        "models_to_eval = {\n",
        "    'base': base_model,\n",
        "    'adv-regularized': adv_model.base_model\n",
        "}\n",
        "metrics = {\n",
        "    name: tf.keras.metrics.SparseCategoricalAccuracy()\n",
        "    for name in models_to_eval.keys()\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BAPYegAbC8mZ"
      },
      "source": [
        "摂動された例を生成し、それを用いてモデルを評価するループをここに示します。摂動された画像、ラベル、予測は次のセクションで可視化するために保存しておきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IGnLXhswmUN8"
      },
      "outputs": [],
      "source": [
        "perturbed_images, labels, predictions = [], [], []\n",
        "\n",
        "for batch in test_set_for_adv_model:\n",
        "  perturbed_batch = reference_model.perturb_on_batch(batch)\n",
        "  # Clipping makes perturbed examples have the same range as regular ones.\n",
        "  perturbed_batch[IMAGE_INPUT_NAME] = tf.clip_by_value(                          \n",
        "      perturbed_batch[IMAGE_INPUT_NAME], 0.0, 1.0)\n",
        "  y_true = perturbed_batch.pop(LABEL_INPUT_NAME)\n",
        "  perturbed_images.append(perturbed_batch[IMAGE_INPUT_NAME].numpy())\n",
        "  labels.append(y_true.numpy())\n",
        "  predictions.append({})\n",
        "  for name, model in models_to_eval.items():\n",
        "    y_pred = model(perturbed_batch)\n",
        "    metrics[name](y_true, y_pred)\n",
        "    predictions[-1][name] = tf.argmax(y_pred, axis=-1).numpy()\n",
        "\n",
        "for name, metric in metrics.items():\n",
        "  print('%s model accuracy: %f' % (name, metric.result().numpy()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S5cC3XbRGFJQ"
      },
      "source": [
        "入力が敵対的に摂動されると、基本モデルの精度が劇的に（99% から約 50% に）低下することが分かります。一方で、敵対的正則化されたモデルの精度低下はごくわずか（99% から 95% に）です。これは、敵対的学習がモデルのロバスト性向上に有効であることを示しています。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YfB5oBBfWLRK"
      },
      "source": [
        "## 敵対的摂動された画像の例\n",
        "\n",
        "ここで、敵対的摂動された画像を確認してみます。摂動された画像は人間が認識可能な数字を表示していますが、基本モデルをうまく騙せることが分かります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3iK9vO_xKJfg"
      },
      "outputs": [],
      "source": [
        "batch_index = 0\n",
        "\n",
        "batch_image = perturbed_images[batch_index]\n",
        "batch_label = labels[batch_index]\n",
        "batch_pred = predictions[batch_index]\n",
        "\n",
        "batch_size = HPARAMS.batch_size\n",
        "n_col = 4\n",
        "n_row = (batch_size + n_col - 1) / n_col\n",
        "\n",
        "print('accuracy in batch %d:' % batch_index)\n",
        "for name, pred in batch_pred.items():\n",
        "  print('%s model: %d / %d' % (name, np.sum(batch_label == pred), batch_size))\n",
        "\n",
        "plt.figure(figsize=(15, 15))\n",
        "for i, (image, y) in enumerate(zip(batch_image, batch_label)):\n",
        "  y_base = batch_pred['base'][i]\n",
        "  y_adv = batch_pred['adv-regularized'][i]\n",
        "  plt.subplot(n_row, n_col, i+1)\n",
        "  plt.title('true: %d, base: %d, adv: %d' % (y, y_base, y_adv))\n",
        "  plt.imshow(tf.keras.preprocessing.image.array_to_img(image), cmap='gray')\n",
        "  plt.axis('off')\n",
        "\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g_vo1pWYJlHP"
      },
      "source": [
        "## 結論\n",
        "\n",
        "Neural Structured Learning（NSL）フレームワークを使用して画像分類に敵対的学習を用いることを実証しました。ユーザーの方々には、（ハイパーパラメータの）さまざまな敵対的設定を実験し、それがモデルのロバスト性にどのような影響を与えるかを確認してみることを推奨してしています。"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "adversarial_keras_cnn_mnist.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
